package demo.tls;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import demo.tls.socket.SocketChannel;
import demo.tls.socket.SocketChannelPool;
import lombok.Data;
import lombok.ToString;

public class Tlsdemo {

    public static void main(String[] args) throws NoSuchAlgorithmException, IOException, ClassNotFoundException, InstantiationException,
                                                  IllegalAccessException {
        try {
            Connection conn =
                DriverManager.getConnection("jdbc:mysql://localhost:3306/test?" +
                                            "user=root&password=123456&useUnicode=true&characterEncoding=UTF-8" +
                                            "&serverTimezone" +
                                            "=Asia/Shanghai&useAffectedRows=true");
            PreparedStatement statement = conn.prepareStatement("select TABLE_SCHEMA from INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA = ? and " +
                                                                "TABLE_NAME = ?");
            statement.setString(1, "test");
            statement.setString(2, "user");
            ResultSet resultSet = statement.executeQuery();
            while (resultSet.next()) {
                String tableScheme = resultSet.getString("TABLE_SCHEMA");
                System.out.println(tableScheme);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void start() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        SocketChannel socketChannel = SocketChannelPool.open(new InetSocketAddress("192.168.2.5", 3306));
        Header header = readHeader(socketChannel);
        Body body = readBody(socketChannel.read(header.packetBodyLength));
        body.header = header;
        System.out.println(body);
    }

    /**
     * 读取头部信息，一共四个字节：前三个字节代表后续body的长度，高位字节代表包数量
     *
     * @param socketChannel
     * @return
     * @throws IOException
     */
    public Header readHeader(SocketChannel socketChannel) throws IOException {
        Header header = new Header();
        header.fromBytes(socketChannel.read(4, 5000));
        return header;
    }

    public Body readBody(byte[] body) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        // Body b = new Body();
        Body b = (Body) Thread.currentThread().getContextClassLoader().loadClass(Body.class.getTypeName()).newInstance();
        b.fromBytes(body);
        return b;
    }


    @Data
    public static class Header {

        private int packetBodyLength;

        private byte packetSequenceNumber;

        public byte[] toBytes() {
            byte[] bytes = new byte[4];
            bytes[0] = (byte) (packetBodyLength & 0xff);
            bytes[1] = (byte) ((packetBodyLength & 0xff) >>> 8);
            bytes[2] = (byte) ((packetBodyLength & 0xff) >>> 16);
            bytes[3] = packetSequenceNumber;
            return bytes;
        }

        public void fromBytes(byte[] bytes) {
            //使用 & 0xff 可以获取到字节中的低位，使用左移的目的是将数据返回到原有的位置，使用 | 目的是将数据拼接回原来的位
            packetBodyLength = (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8) | ((bytes[2] & 0xff) << 16);
            //最高位代表包数量
            packetSequenceNumber = bytes[3];
        }
    }


    @Data
    @ToString
    public static class Body {
        private Header header;

        private byte protocol;
        private String version;
        private int threadId;
        private String salt;
        private int serverCapabilities;
        private int serverLanguage;
        private int serverStatus;
        private int extendedCapabilities;
        private int pluginLength;
        private String pluginSalt;
        private String authPluginName;

        public void fromBytes(byte[] bytes) {
            int index = 0;
            this.protocol = bytes[index++];
            this.version = version(bytes, index);
            index += (this.version.length() + 1);

            this.threadId = threadId(bytes, index);
            index += 4;

            this.salt = salt(bytes, index);
            index += (salt.length() + 1);

            this.serverCapabilities = serverCapabilities(bytes, index);
            index += 2;

            this.serverLanguage = serverLanguage(bytes, index);
            index += 1;

            this.serverStatus = serverStatus(bytes, index);
            index += 2;

            this.extendedCapabilities = extendedCapabilities(bytes, index);
            index += 2;

            this.pluginLength = pluginLength(bytes, index);
            index += 11;

            this.pluginSalt = pluginSalt(bytes, index);
            index += (this.pluginSalt.length() + 1);

            this.authPluginName = authPluginName(bytes, index, this.pluginLength);
        }

        private String version(byte[] bytes, int index) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            for (int i = index; i < bytes.length; i++) {
                byte v = bytes[i];
                if (v == 0x00) {
                    break;
                }
                outputStream.write(v);
            }
            return outputStream.toString();
        }

        private int threadId(byte[] bytes, int index) {
            return bytes[index++] & 0xff | ((bytes[index++]) << 8) | ((bytes[index++]) << 16) | ((bytes[index++]) << 24);
        }

        private String salt(byte[] bytes, int index) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            for (int i = index; i < bytes.length; i++) {
                byte v = bytes[i];
                if (v == 0x00) {
                    break;
                }
                outputStream.write(v);
            }
            return outputStream.toString();
        }

        private int serverCapabilities(byte[] bytes, int index) {
            return bytes[index++] & 0xff | ((bytes[index] & 0xff) << 8);
        }

        private int serverLanguage(byte[] bytes, int index) {
            return bytes[index] & 0xff;
        }

        private int serverStatus(byte[] bytes, int index) {
            return bytes[index] & 0xff | ((bytes[index + 1] & 0xff) << 8);
        }

        private int extendedCapabilities(byte[] bytes, int index) {
            return bytes[index] & 0xff | ((bytes[index + 1] & 0xff) << 8);
        }

        private int pluginLength(byte[] bytes, int index) {
            return bytes[index];
        }

        private String pluginSalt(byte[] bytes, int index) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            for (int i = index; i < bytes.length; i++) {
                byte v = bytes[i];
                if (v == 0x00) {
                    break;
                }
                outputStream.write(v);
            }
            return outputStream.toString();
        }

        private String authPluginName(byte[] bytes, int index, int pluginLength) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            for (int i = index; i < bytes.length; i++) {
                byte v = bytes[i];
                if (v == 0x00 || pluginLength <= 0) {
                    break;
                }
                outputStream.write(v);
                pluginLength--;
            }
            return outputStream.toString();
        }
    }


}
